package com.seuic.posdsdk.pos_func;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;

import com.seuic.constants.EmvConstants;
import com.seuic.constants.PinpadConstants;
import com.seuic.constants.ReceiptConstants;
import com.seuic.device_implement.PosDeviceImp;
import com.seuic.posdsdk.device_interface.EmvDevice;
import com.seuic.posdsdk.device_interface.PinpadDevice;
import com.seuic.posdsdk.device_interface.PosDevice;
import com.seuic.posdsdk.device_interface.PrinterDevice;
import com.seuic.posdsdk.device_interface.PsamDevice;
import com.seuic.posdsdk.device_interface.ReceiptEngine;

import java.util.List;

/**
 * The POS SDK Methods
 */
public class PosFuncs {

    private static PosFuncs self;

    private PosFuncs() {

    }

    /**
     * Get the only instance of PosFuncs
     *
     * @return the only instance of PosFuncs
     */
    public static PosFuncs getInstace() {
        if (self == null) {
            synchronized (PosFuncs.class) {
                if (self == null) {
                    self = new PosFuncs();
                }
            }
        }
        return self;
    }

    private Handler handler = new Handler(Looper.getMainLooper());
    private Context context;

    private PosDevice posDevice = null;
    private EmvDevice emvDevice = null;
    private PinpadDevice pinpadDevice = null;
    private PrinterDevice printerDevice = null;
    private ReceiptEngine receiptEngine = null;
    private PsamDevice psamDevice = null;

    private String lastError = null;

    private int key_index = 0;
    private int last_amount = 0;

    /**
     * Init POS SDK
     * Should be called before use any other SDK Methods
     *
     * @param context The Android context, should not be null
     * @return 0 when success inited, -1 when failed
     */
    public int init(Context context) {
        if (context == null) {
            lastError = "Input Param Error";
            return -1;
        }
        this.context = context.getApplicationContext();
        posDevice = PosDeviceImp.getInstace();
        emvDevice = posDevice.getEmv();
        pinpadDevice = posDevice.getPinpad();
        printerDevice = posDevice.getPrinter();
        receiptEngine = posDevice.getReceiptEngin();
        psamDevice = posDevice.getPsamDevice();
        return posDevice.init(context);
    }

    /**
     * Close POS SDK
     * Should be called when other POS functions will not be used.
     */
    public void close() {
        if (posDevice != null) {
            posDevice.close();
            posDevice = null;
            emvDevice = null;
            pinpadDevice = null;
            printerDevice = null;
            receiptEngine = null;
            psamDevice = null;
        }
    }

    /**
     * Get last error of POS SDK
     *
     * @return The description of last error
     */
    public String getLastError() {
        return lastError;
    }

    /**
     * Set the key index used in the POS SDK
     * The default index is 0
     * Just call this if you want to use key index other than 0
     *
     * @param index key index to set
     */
    public void setKeyIndex(int index) {
        key_index = index;
    }

    /**
     * Get the model of product
     *
     * @return The product model
     */
    public String getProductModel() {
        if (posDevice == null) {
            lastError = "SDK not Init";
            return null;
        }
        return posDevice.getProductModel();
    }

    /**
     * Get the serial number of terminal
     *
     * @return The serial number
     */
    public String getTermSerial() {
        if (posDevice == null) {
            lastError = "SDK not Init";
            return null;
        }
        return posDevice.getTermSerial();
    }

    /**
     * Get the system version of terminal
     *
     * @return The system version
     */
    public String getSystemVersion() {
        if (posDevice == null) {
            lastError = "SDK not Init";
            return null;
        }
        return posDevice.getSystemVersion();
    }

    /**
     * Get the service version of terminal
     *
     * @return The service version
     */
    public String getServiceVersion() {
        if (posDevice == null) {
            lastError = "SDK not Init";
            return null;
        }
        return posDevice.getServiceVersion();
    }

    /**
     * Download the protect key used to encrypt main key
     *
     * @param plainKey The protect key, not encrypted
     * @return 0 when download success, or other when failed
     */
    public int downloadProtectKey(String plainKey) {
        if (pinpadDevice == null) {
            lastError = "SDK not Init";
            return -1;
        }
        int result = pinpadDevice.downloadProtetKey(key_index, plainKey);
        if (result < 0) {
            lastError = pinpadDevice.getLastError();
        }
        return result;
    }

    /**
     * Download encrypted main key
     *
     * @param encKey     Main key, encrypted by protect key
     * @param checkValue Checkvalue of main key
     * @return 0 when download success, or other when failed
     */
    public int downloadMainKey(String encKey, String checkValue) {
        if (pinpadDevice == null) {
            lastError = "SDK not Init";
            return -1;
        }
        int result = pinpadDevice.downloadMainkey(key_index, encKey, checkValue);
        if (result < 0) {
            lastError = pinpadDevice.getLastError();
        }
        return result;
    }

    /**
     * Download work keys used in transactions
     * All the workkeys should be encrypted by mainkey
     *
     * @param pinKey           Encrypted pin key, used to encrypt pin
     * @param pinKeyCheckValue Pin key checkvalue
     * @param macKey           Encrypted mac key, used to calculate mac
     * @param macKeyCheckValue Mac key checkvalue
     * @param tdKey            Encrypted track key, used to encrypt magcard tracks
     * @param tdKeyCheckValue  Track key checkvalue
     * @return 0 when download success, or other when failed
     */
    public int downloadWorkKeys(String pinKey, String pinKeyCheckValue,
                                String macKey, String macKeyCheckValue,
                                String tdKey, String tdKeyCheckValue) {
        if (pinpadDevice == null) {
            lastError = "SDK not Init";
            return -1;
        }
        int result = pinpadDevice.downloadWorkKeys(key_index, pinKey, pinKeyCheckValue,
                macKey, macKeyCheckValue, tdKey, tdKeyCheckValue);
        if (result < 0) {
            lastError = pinpadDevice.getLastError();
        }
        return result;
    }

    /**
     * Use mac key to do encryption
     *
     * @param msg The Msg to encrypt, the length should be the multiple of 16
     * @return The encrypt result, or null when failed
     */
    public String macKeyEncrypt(String msg) {
        if (pinpadDevice == null) {
            lastError = "SDK not Init";
            return null;
        }
        return pinpadDevice.macKeyEncrypt(key_index, msg);
    }

    /**
     * Use mac key to do decryption
     *
     * @param msg The Msg to decrypt, the length should be the multiple of 16
     * @return The encrypt result, or null when failed
     */
    public String macKeyDecrypt(String msg) {
        if (pinpadDevice == null) {
            lastError = "SDK not Init";
            return null;
        }
        return pinpadDevice.macKeyDecrypt(key_index, msg);
    }

    /**
     * Use track key to do encryption
     *
     * @param msg The Msg to encrypt, the length should be the multiple of 16
     * @return The encrypt result, or null when failed
     */
    public String trackKeyEncrypt(String msg) {
        if (pinpadDevice == null) {
            lastError = "SDK not Init";
            return null;
        }
        return pinpadDevice.trackKeyEncrypt(key_index, msg);
    }

    /**
     * Use track key to do decryption
     *
     * @param msg The Msg to decrypt, the length should be the multiple of 16
     * @return The encrypt result, or null when failed
     */
    public String trackKeyDecrypt(String msg) {
        if (pinpadDevice == null) {
            lastError = "SDK not Init";
            return null;
        }
        return pinpadDevice.trackKeyDecrypt(key_index, msg);
    }

    /**
     * Get Tusn Data
     * For transactions in China only, not used in overseas
     *
     * @param random Random number
     * @return TUSN Data, or null when failed
     */
    public Bundle getTusnData(String random) {
        if (pinpadDevice == null) {
            lastError = "SDK not Init";
            return null;
        }
        Bundle bundle = pinpadDevice.getTusn(random);
        if (bundle == null) {
            lastError = posDevice.getLastError();
        }
        return bundle;
    }

    /**
     * Download emv app id list use in iccard transaction
     *
     * @param aids The AID list
     * @return 0 when download success, or other when failed
     */
    public int downloadAids(List<String> aids) {
        if (emvDevice == null) {
            lastError = "SDK not Init";
            return -1;
        }
        int result = emvDevice.downloadAids(aids);
        if (result < 0) {
            lastError = emvDevice.getLastError();
        }
        return result;
    }

    /**
     * Download emv cert list use in iccard transaction
     *
     * @param capks The CAPK list
     * @return 0 when download success, or other when failed
     */
    public int downloadCapks(List<String> capks) {
        if (emvDevice == null) {
            lastError = "SDK not Init";
            return -1;
        }
        int result = emvDevice.downloadCapks(capks);
        if (result < 0) {
            lastError = emvDevice.getLastError();
        }
        return result;
    }

    private PosCallbacks.EnterPinCallbacks enterPinCallbacks = null;

    /**
     * Start enter pin function
     * When Called, the enter pin process will be started
     * The events and result of the process will be returned in callbacks
     *
     * @param callbacks The callbacks in enter pin process
     * @param pan       The primary account number of the card
     * @param amount    The amount of transaction
     * @see PosCallbacks.EnterPinCallbacks for callback events
     */
    public void enterPin(PosCallbacks.EnterPinCallbacks callbacks, String pan, int amount) {
        if (callbacks == null) {
            lastError = "Input Param Error";
            return;
        }
        this.enterPinCallbacks = callbacks;
        if (pinpadDevice == null) {
            enterPinCallbackOnFailed("SDK not Init");
            return;
        }
        Bundle bundle = new Bundle();
        bundle.putInt(PinpadConstants.PINKEY_INDEX, key_index);
        bundle.putInt(PinpadConstants.AMOUNT, amount);
        bundle.putString(PinpadConstants.PAN, pan);
        int[] pin_len_limit = new int[]{0, 4, 6};
        bundle.putIntArray(PinpadConstants.PIN_LENGTH_LIMITS, pin_len_limit);
        pinpadDevice.enterPin(new PinpadDevice.EnterPinCallback() {

            @Override
            public void onSuccess(final String enc, final boolean is_no_pin) {
                if (enterPinCallbacks == null) {
                    return;
                }
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        enterPinCallbacks.onSuccess(enc, is_no_pin);
                    }
                });
            }

            @Override
            public void onFail(String error) {
                enterPinCallbackOnFailed(error);
            }

            @Override
            public void onInput(int input_num) {

            }
        }, bundle);
    }

    private void enterPinCallbackOnFailed(final String error) {
        if (enterPinCallbacks == null) {
            return;
        }
        handler.post(new Runnable() {
            @Override
            public void run() {
                enterPinCallbacks.onFail(error);
            }
        });
    }

    private PosCallbacks.ReadCardCallbacks readCardCallbacks = null;

    /**
     * Config the on-off status of the card readers
     *
     * @param magcardOn true to on, false to off, default on
     * @param iccardOn  true to on, false to off, default on
     * @param rfcardOn  true to on, false to off, default on
     * @return config result, 0 to success, other to failed
     */
    public int cardReaderConfig(boolean magcardOn, boolean iccardOn, boolean rfcardOn) {
        if (emvDevice == null) {
            lastError = "SDK not Init";
            return -1;
        }
        return emvDevice.readerConfig(magcardOn, iccardOn, rfcardOn);
    }

    /**
     * Start a process to wait magcard swipe, IC card insert or RF Card present
     *
     * @param readCardCallbacks The callbacks of read card
     * @see com.seuic.posdsdk.pos_func.PosCallbacks.ReadCardCallbacks
     */
    public void readCard(final PosCallbacks.ReadCardCallbacks readCardCallbacks) {
        if (readCardCallbacks == null) {
            lastError = "Input Params Error";
        }
        this.readCardCallbacks = readCardCallbacks;
        emvDevice.readCard(new EmvDevice.EmvDeviceReadCardCallback() {
            @Override
            public void onICCardIn() {
                if (readCardCallbacks == null) {
                    return;
                }
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        readCardCallbacks.onICCardIn();
                    }
                });
            }

            @Override
            public void onRFCardIn() {
                if (readCardCallbacks == null) {
                    return;
                }
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        readCardCallbacks.onRFCardIn();
                    }
                });
            }

            @Override
            public void onMagcardIn(final Bundle data) {
                if (readCardCallbacks == null) {
                    return;
                }
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        readCardCallbacks.onMagcardIn(data);
                    }
                });
            }

            @Override
            public void OnFailed(final String error) {
                if (readCardCallbacks == null) {
                    return;
                }
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        readCardCallbacks.OnFailed(error);
                    }
                });
            }
        }, 120000);
    }


    /**
     * Power on the icCard already insert or the RFCard already presented
     * Not that you should make sure the card is present, or the method will fail.
     * A way to use this method is call readCard method first, when the card present
     * event is callback, call this function immediately.
     *
     * @return the power on result, 0 to success, other to failed
     */
    public int icCardPowerOn() {
        if (emvDevice == null) {
            lastError = "SDK not Init";
            return -1;
        }
        emvDevice.powerOn();
        return 0;
    }

    /**
     * Exchange apdu with IC card or RF card
     * Note that you should call the icCardPowerOn method first before send any APDU.
     *
     * @param cmd the APDU command
     * @return the APDU response
     */
    public String icCardApdu(String cmd) {
        if (emvDevice == null) {
            lastError = "SDK not Init";
            return null;
        }
        return emvDevice.exchangeApdu(cmd);
    }

    /**
     * Power off the IC card or RF card
     * Note that you should call this method after finsh send APDUs
     *
     * @return the power off result, 0 to success, other to failed
     */
    public int icCardPowerOff() {
        if (emvDevice == null) {
            lastError = "SDK not Init";
            return -1;
        }
        emvDevice.powerOff();
        return 0;
    }

    private class EmvParams {
        String termId = null;
        String merchantName = null;
        String merchantId = null;
        boolean support_ecc = false;
        boolean force_online = false;
        boolean support_qps = false;
        int qps_limit = 0;
        boolean qps_bin_A_enable = false;
        boolean qps_bin_B_enable = false;
        boolean cdcvm_support = false;
    }

    private EmvParams emvParams = new EmvParams();

    /**
     * Set the terminal information in EMV transactions
     *
     * @param termId       The terminal ID (tag 9F1C & 9F1E), the default value is 000000000000000
     * @param merchantName The merchant Name (tag 9F4E), the default value is 00000000
     * @param merchantId   The merchant ID ( tag 9F16), the default value is 000000000000000
     */
    public void emvSetTermInfo(String termId, String merchantName, String merchantId) {
        emvParams.termId = termId;
        emvParams.merchantName = merchantName;
        emvParams.merchantId = merchantId;
    }

    /**
     * Set whether the ECash function issupport in EMV transactions
     *
     * @param support_ecc true to support, false to unsupport, default false
     */
    public void emvSetSupportEcc(boolean support_ecc) {
        emvParams.support_ecc = support_ecc;
    }

    /**
     * Set whether the EMV transactions are force online
     *
     * @param force_oline true to support, false to unsupport, default false
     */
    public void emvSetForceOnline(boolean force_oline) {
        emvParams.force_online = force_oline;
    }

    /**
     * Enable the QPS function in EMV transactions
     * In default, the function is disabled
     * For transactions in China only, not used in overseas
     *
     * @param qps_limit    The amount limit of QPS function, the default is 0
     * @param bin_A_enable whether the BIN A is used in QPS function, the default is false
     * @param bin_B_enable whether the BIN B is used in QPS function, the default is false
     */
    public void emvEnableQPS(int qps_limit, boolean bin_A_enable, boolean bin_B_enable) {
        emvParams.support_qps = true;
        emvParams.qps_limit = qps_limit;
        emvParams.qps_bin_A_enable = bin_A_enable;
        emvParams.qps_bin_B_enable = bin_B_enable;
    }

    /**
     * Disable the QPS function in EMV Transaction
     * In default, the function is disabled
     * For transactions in China only, not used in overseas
     */
    public void emvDisableQPS() {
        emvParams.support_qps = false;
    }

    /**
     * Set whether the CDCVM function be supported in EMV transactions
     *
     * @param cdcvm_support true to support, false to not support, the default value is false
     */
    public void emvSetCDCVMSupport(boolean cdcvm_support) {
        emvParams.cdcvm_support = cdcvm_support;
    }

    /**
     * Start the EMV transaction process
     * When Called, the EMV transaction process will be started
     * The events and result of the process will be returned in callbacks
     *
     * @param callbacks The callbacks in EMV transaction process
     * @param amount    The amount of transaction
     * @param tag_9c    The transactions type (tag 9C)
     * @see PosCallbacks.EmvTransStartCallbacks for callback events
     */
    public void emvTransStart(PosCallbacks.EmvTransStartCallbacks callbacks,
                              int amount, int tag_9c) {
        emvTransStart(callbacks, false, amount, tag_9c);
    }

    private PosCallbacks.EmvTransStartCallbacks emvTransStartCallbacks = null;

    private void emvTransStart(PosCallbacks.EmvTransStartCallbacks callbacks,
                               boolean is_simple_flow, int amount, int tag_9c) {
        if (callbacks == null) {
            lastError = "Input Param Error";
            return;
        }
        this.emvTransStartCallbacks = callbacks;
        if (emvDevice == null) {
            emvTransStartCallbackOnFail("SDK not Init");
            return;
        }
        if (amount < 0 || tag_9c < 0) {
            emvTransStartCallbackOnFail("Input Param Error");
            return;
        }
        last_amount = amount;
        Bundle bundle = new Bundle();
        if (is_simple_flow) {
            bundle.putBoolean(EmvConstants.IS_SIMPLE_FLOW, true);
        } else {
            if (emvParams.termId != null) {
                bundle.putString(EmvConstants.TERM_ID, emvParams.termId);
            }
            if (emvParams.merchantId != null) {
                bundle.putString(EmvConstants.MERCHANT_ID, emvParams.merchantId);
            }
            if (emvParams.merchantName != null) {
                bundle.putString(EmvConstants.MERCHANT_NAME, emvParams.merchantName);
            }
            bundle.putBoolean(EmvConstants.IS_SUPPORT_ECC, emvParams.support_ecc);
            bundle.putBoolean(EmvConstants.IS_FORCE_ONLINE, emvParams.force_online);
            bundle.putBoolean(EmvConstants.IS_QPS_SUPPORT, emvParams.support_qps);
            bundle.putBoolean(EmvConstants.IS_BIN_A_SUPPORT, emvParams.qps_bin_A_enable);
            bundle.putBoolean(EmvConstants.IS_BIN_B_SUPPORT, emvParams.qps_bin_B_enable);
            bundle.putInt(EmvConstants.QPS_LIMIT, emvParams.qps_limit);
            bundle.putBoolean(EmvConstants.IS_CDCVM_SUPPORT, emvParams.cdcvm_support);
        }
        bundle.putInt(EmvConstants.AMOUNT, amount);
        bundle.putInt(EmvConstants.TAG_9C, tag_9c);
        emvDevice.transStart(new EmvTransStartCallback(), bundle);
    }

    private void emvTransStartCallbackOnFail(final String error) {
        if (emvTransStartCallbacks == null) {
            return;
        }
        handler.post(new Runnable() {
            @Override
            public void run() {
                emvTransStartCallbacks.onFailed(error);
            }
        });
    }

    private class EmvTransStartCallback implements EmvDevice.TransStartCallback {

        @Override
        public void onCardInsert(final String type) {
            if (emvTransStartCallbacks == null) {
                return;
            }
            handler.post(new Runnable() {
                @Override
                public void run() {
                    emvTransStartCallbacks.onDetectCard(type);
                }
            });
        }

        @Override
        public void onAppSelect(final List<String> appList) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    AlertDialog.Builder builder = new AlertDialog.Builder(context);
                    builder.setTitle("App Select");
                    String[] apps = (String[]) appList.toArray();
                    builder.setSingleChoiceItems(apps, -1, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                            emvDevice.selectApp(which);
                        }
                    });
                    builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                            emvDevice.selectApp(-1);
                        }
                    });
                    builder.show();
                }
            });
        }

        @Override
        public void onConfirmCert(final String certType, final String certNum) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    AlertDialog.Builder builder = new AlertDialog.Builder(context);
                    builder.setTitle("Certificate Confirm");
                    builder.setMessage(certType + ": " + certNum);
                    builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                            emvDevice.confirmCert(EmvConstants.CONFIRM_OK);
                        }
                    });
                    builder.setNegativeButton("Reject", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                            emvDevice.confirmCert(EmvConstants.CONFIRM_FAIL);
                        }
                    });
                    builder.setNeutralButton("Bypass", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                            emvDevice.confirmCert(EmvConstants.CONFIRM_BYPASS);
                        }
                    });
                    builder.show();
                }
            });
        }

        private String pan = null;

        @Override
        public void onConfirmCardInfo(final Bundle cardInfoToConfirm) {
            if (emvTransStartCallbacks == null) {
                return;
            }
            handler.post(new Runnable() {
                @Override
                public void run() {
                    pan = cardInfoToConfirm.getString(EmvConstants.PAN);
                    emvTransStartCallbacks.onConfirmCardInfo(pan);
                }
            });
        }

        @Override
        public void onEnterPin(boolean is_offline_pin, int leftTimes) {
            if (is_offline_pin) {
                //Most EMV Cards not use this function, so not implement this fuction
                //Just return cancel instead
                lastError = "Not Support Offline Pin";
                emvDevice.finishEnterPin(false, "0000000000000000");
            } else {
                if (pinpadDevice == null) {
                    lastError = "";
                    return;
                }
                Bundle bundle = new Bundle();
                bundle.putString(PinpadConstants.PAN, pan);
                bundle.putInt(PinpadConstants.PINKEY_INDEX, key_index);
                bundle.putInt(PinpadConstants.AMOUNT, last_amount);
                pinpadDevice.enterPin(new EmvEnterPinCallback(), bundle);
            }
        }

        private class EmvEnterPinCallback implements PinpadDevice.EnterPinCallback {

            @Override
            public void onSuccess(final String enc, final boolean is_no_pin) {
                if (emvTransStartCallbacks == null) {
                    return;
                }
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            emvTransStartCallbacks.onOlinePinEntered(enc, is_no_pin);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
                emvDevice.finishEnterPin(true, "0000000000000000");
            }

            @Override
            public void onFail(String error) {
                lastError = error;
                emvDevice.finishEnterPin(false, "0000000000000000");
            }

            @Override
            public void onInput(int input_num) {
                //do nothing without pinpad
            }
        }

        @Override
        public void onSuccess(final Bundle data) {
            if (emvTransStartCallbacks == null) {
                return;
            }
            handler.post(new Runnable() {
                @Override
                public void run() {
                    emvTransStartCallbacks.onSuccess(data);
                }
            });
        }

        @Override
        public void onFallback() {
            if (emvTransStartCallbacks == null) {
                return;
            }
            handler.post(new Runnable() {
                @Override
                public void run() {
                    emvTransStartCallbacks.onFailed("ICCard Fallback");
                }
            });
        }

        @Override
        public void onUseICCard() {
            if (emvTransStartCallbacks == null) {
                return;
            }
            handler.post(new Runnable() {
                @Override
                public void run() {
                    emvTransStartCallbacks.onFailed("Please use IC Card");
                }
            });
        }

        @Override
        public void onFailed(String error) {
            emvTransStartCallbackOnFail(error);
        }
    }

    /**
     * Start the simple flow of EMV transaction
     * The simple flow will only read the card data
     *
     * @param callbacks The callbacks in EMV simple flow transaction process
     * @see PosCallbacks.EmvTransStartCallbacks for callback events
     */
    public void emvSimpleFlow(PosCallbacks.EmvTransStartCallbacks callbacks) {
        if (callbacks == null) {
            lastError = "Input Param Error";
            return;
        }
        emvTransStart(callbacks, true, 0, 0);
    }

    /**
     * cancel the EMV process already started
     */
    public void emvCancel() {
        if (emvDevice == null) {
            lastError = "SDK not Init";
            return;
        }
        emvDevice.cancel();
    }

    /**
     * Return the confirm card information result
     * In EMV transaction process,
     * some times the onConfirmCardInfo Method in PosCallbacks.EmvTransStartCallbacks will be called,
     * then the App should show an UI to let the custom to confirm the card infomation.
     * Use this method to notify the result and continue the emv process,
     * after the custom confirm or reject.
     *
     * @param confirmResult The confirmation result
     *                      CONFIRM_OK to confirm the card information
     *                      CONFIRM_FAIL to reject
     *                      CONFIRM_BYPASS to bypass
     * @see EmvConstants for the constant value
     */
    public void emvConfirmCardInfo(String confirmResult) {
        if (emvDevice == null) {
            return;
        }
        emvDevice.confirmCardInfo(confirmResult);
    }

    private PosCallbacks.EmvOnlineResultCallbacks emvOnlineResultCallbacks = null;

    /**
     * Input online result in EMV transaction
     * In some circumstance, the result of EmvTransStart process is ARQC.
     * Then the app will connect to the server to authenticate the card data and get the server response.
     * Use this method to return the online result to the card, and get the final TC value
     *
     * @param callbacks The result events of input online result process
     * @param data      The data form server, the bundle keys defined in EmvConstants
     *                  RESPONSE_CODE(String), the respons code of server
     *                  ISSUER_FIELD_55(String), the filed 55 data returned form server
     * @see PosCallbacks.EmvTransStartCallbacks for callback events
     * @see EmvConstants
     */
    public void emvOnlineResult(PosCallbacks.EmvOnlineResultCallbacks callbacks, Bundle data) {
        if (callbacks == null) {
            lastError = "Input Param Error";
            return;
        }
        this.emvOnlineResultCallbacks = callbacks;
        if (emvDevice == null) {
            emvOnlineResultCallbackOnFail("SDK not Init");
            return;
        }
        if (data == null) {
            emvOnlineResultCallbackOnFail("Input Param Error");
            return;
        }
        emvDevice.inputOnlineResult(new EmvInputOnlineResultCallback(), data);
    }

    private void emvOnlineResultCallbackOnFail(final String error) {
        if (emvOnlineResultCallbacks == null) {
            return;
        }
        handler.post(new Runnable() {
            @Override
            public void run() {
                emvOnlineResultCallbacks.onFailed(error);
            }
        });
    }

    private class EmvInputOnlineResultCallback implements EmvDevice.InputOnlineResultCallback {

        @Override
        public void onSuccess(final Bundle data) {
            if (emvOnlineResultCallbacks == null) {
                return;
            }
            handler.post(new Runnable() {
                @Override
                public void run() {
                    emvOnlineResultCallbacks.onSuccess(data);
                }
            });

        }

        @Override
        public void onFailed(String error) {
            emvOnlineResultCallbackOnFail(error);
        }
    }

    /**
     * Clear buffer of printer
     *
     * @return 0 to success, other to failed
     */
    public int printerClearBuffer() {
        if (receiptEngine == null) {
            lastError = "SDK not Init";
            return -1;
        }
        return receiptEngine.clearBuffer();
    }

    /**
     * Append a line of text to the printer buffer
     *
     * @param text The text to append
     * @return 0 to success, other to failed
     */
    public int printerAppendTextline(String text) {
        if (receiptEngine == null) {
            lastError = "SDK not Init";
            return -1;
        }
        return receiptEngine.addTextLine(text, new Bundle());
    }

    /**
     * Append a line of text with large size and center align to the printer buffer
     *
     * @param text The text to append
     * @return 0 to success, other to failed
     */
    public int printerAppendLargeTextAlignCenter(String text) {
        if (receiptEngine == null) {
            lastError = "SDK not Init";
            return -1;
        }
        Bundle bundle = new Bundle();
        bundle.putInt(ReceiptConstants.FONT_SIZE, 32);
        bundle.putBoolean(ReceiptConstants.IS_BOLD, true);
        bundle.putString(ReceiptConstants.ALIGN, ReceiptConstants.ALIGN_CENTER);
        return receiptEngine.addTextLine(text, bundle);
    }

    /**
     * Append a bitmap to the print buffer
     *
     * @param bitmap The bitmap
     * @return 0 to success, other to failed
     */
    public int printerAppendBitmap(Bitmap bitmap) {
        if (receiptEngine == null) {
            lastError = "SDK not Init";
            return -1;
        }
        return receiptEngine.addBitmap(bitmap, new Bundle());
    }

    /**
     * Append a QR code to the print buffer
     *
     * @param qr_code The QR code value
     * @return 0 to success, other to failed
     */
    public int printerAppendQRcode(String qr_code) {
        if (receiptEngine == null) {
            lastError = "SDK not Init";
            return -1;
        }
        return receiptEngine.addQRCode(qr_code, new Bundle());
    }

    PosCallbacks.PrintCallbacaks printCallbacaks;

    /**
     * Start the print process to print all contents in print buffer
     *
     * @param callbacaks The result events of print process
     * @see PosCallbacks.PrintCallbacaks for callback events
     */
    public void startPrint(PosCallbacks.PrintCallbacaks callbacaks) {
        if (callbacaks == null) {
            lastError = "Input Param Error";
            return;
        }
        this.printCallbacaks = callbacaks;
        if (receiptEngine == null) {
            printCallbackOnFailed("SDK Not Init");
        }
        receiptEngine.print(new ReceiptEngine.PrintReceiptCallback() {
            @Override
            public void onSuccess() {
                if (printCallbacaks == null) {
                    return;
                }
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        printCallbacaks.onSuccess();
                    }
                });
            }

            @Override
            public void onFail(String error) {
                printCallbackOnFailed(error);
            }
        });
    }

    private void printCallbackOnFailed(final String error) {
        if (printCallbacaks == null) {
            return;
        }
        handler.post(new Runnable() {
            @Override
            public void run() {
                printCallbacaks.onFailed(error);
            }
        });
    }

    /**
     * Open Psam Card
     * make sure the psam card is rightly insert into the slot before use this method
     *
     * @return 0 to success, other to fail
     */
    public int psamOpen() {
        if (psamDevice == null) {
            lastError = "SDK not Init";
            return -1;
        }
        String atr = psamDevice.open();
        if (atr == null) {
            return -1;
        }
        return 0;
    }

    /**
     * Exchange apdu with psam card
     * Note that you should call psamOpen first, before send any APDUs
     *
     * @param cmd The apdu command
     * @return The apdu response, or null for fail
     */
    public String psamApdu(String cmd) {
        if (psamDevice == null) {
            lastError = "SDK not Init";
            return null;
        }
        return psamDevice.exchangeApdu(cmd);
    }

    /**
     * Close the psam card
     * Make sure to call this method when you finish sending APDUs
     */
    public void psamClose() {
        if (psamDevice == null) {
            lastError = "SDK not Init";
            return;
        }
        psamDevice.close();
    }
}
